from machine import Pin, reset
from time import sleep
import usocket as socket
import network
import gc
import _thread
import time

# ---------------- 配置 ----------------
WIFI_SSID      = "LAPTOP-D3RPTUAA 6358"
WIFI_PASSWORD  = "19234949992.ch"
TCP_PORT       = 8080          # ESP32-CAM → ESP32-S3
HTTP_PORT      = 9000          # ESP32-S3 → 浏览器
FIXED_IP       = '192.168.137.100'
GATEWAY        = '192.168.137.1'
SUBNET         = '255.255.255.0'
DNS            = '8.8.8.8'
# -------------------------------------

client_sockets = []            # 当前所有 HTTP 长连接
frame_lock     = _thread.allocate_lock()
last_recv_tick = time.time()   # 最近一次收到数据的时间

# ---------- Wi-Fi ----------
class WiFiConnection:
    def __init__(self, ssid, pwd, ip, gw, mask, dns):
        self.ssid, self.pwd = ssid, pwd
        self.ip_cfg = (ip, mask, gw, dns)
        self.wlan = network.WLAN(network.STA_IF)

    def connect(self):
        network.WLAN(network.AP_IF).active(False)
        self.wlan.active(True)
        sleep(0.5)
        self.wlan.ifconfig(self.ip_cfg)
        if not self.wlan.isconnected():
            self.wlan.connect(self.ssid, self.pwd)
            for t in range(30, 0, -1):
                if self.wlan.isconnected():
                    break
                print(f"Wi-Fi connecting... {t}s")
                sleep(1)
        if self.wlan.isconnected():
            print("Wi-Fi OK:", self.wlan.ifconfig())
            return self.wlan.ifconfig()[0]
        else:
            print("Wi-Fi failed")
            return None

# ---------- TCP 接收线程 ----------
def tcp_rx_thread():
    global last_recv_tick
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind(('0.0.0.0', TCP_PORT))
    srv.listen(1)
    print(f"TCP server listening on {TCP_PORT}")

    while True:
        try:
            cam_sock, addr = srv.accept()
            cam_sock.settimeout(5.0)
            print("ESP32-CAM connected:", addr)

            # 把 MJPEG 流直接透传给所有浏览器
            while True:
                chunk = cam_sock.recv(1024)
                if not chunk:
                    break
                last_recv_tick = time.time()

                # 立即广播给所有 HTTP 客户端
                with frame_lock:
                    bads = []
                    for c in client_sockets:
                        try:
                            c.write(chunk)          # 快速 send
                        except Exception as e:
                            bads.append(c)
                    for c in bads:
                        try:
                            c.close()
                        except:
                            pass
                        client_sockets.remove(c)
            cam_sock.close()
            print("ESP32-CAM disconnected")
        except Exception as e:
            print("TCP error:", e)

# ---------- HTTP / MJPEG ----------
HTML_PAGE = b"""<!DOCTYPE html>
<html>
<head>
    <title>ESP32-S3 Stream</title>
</head>
<body>
    <img src="/stream" style="max-width:100%;">
</body>
</html>"""

def http_thread(ip):
    hdr_stream = b"""\
HTTP/1.1 200 OK\r
Content-Type: multipart/x-mixed-replace; boundary=frame\r
Cache-Control: no-cache\r
Connection: close\r
\r
"""
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind(('0.0.0.0', HTTP_PORT))
    srv.listen(3)
    print(f"HTTP server: http://{ip}:{HTTP_PORT}")

    while True:
        try:
            cli, addr = srv.accept()
            req = cli.recv(1024)
            if b'GET / ' in req or b'GET /index.html' in req:
                cli.send(b"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")
                cli.send(HTML_PAGE)
                cli.close()
            elif b'GET /stream' in req:
                cli.send(hdr_stream)
                cli.setblocking(False)   # 非阻塞，方便后续 write
                with frame_lock:
                    client_sockets.append(cli)
            else:
                cli.close()
        except Exception as e:
            print("HTTP error:", e)

# ---------- 心跳/状态 ----------
def heartbeat():
    while True:
        sleep(5)
        if time.time() - last_recv_tick > 5:
            print("⚠️  No data from ESP32-CAM")
        gc.collect()

# ---------- main ----------
def main():
    gc.enable()
    wifi = WiFiConnection(WIFI_SSID, WIFI_PASSWORD,
                          FIXED_IP, GATEWAY, SUBNET, DNS)
    ip = wifi.connect()
    if not ip:
        sleep(2)
        reset()

    _thread.start_new_thread(tcp_rx_thread, ())
    _thread.start_new_thread(http_thread, (ip,))
    heartbeat()          # 主线程做心跳

if __name__ == "__main__":
    main()
    